/*
 * WindowDelegate.cpp  0.4.0 / Nov 17, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
#include "WindowDelegate.h"

#include "SwingImageDelegate.h"
#include "SwingUtilitiesDelegate.h"
#include "com_cute4j_swing_Window.h"

// WindowDelegate :: Protected Constructors. -----------------------------------

/*!
 * \brief Constructs new WindowDelegate object.
 *
 * \param env the JNIEnv pointer
 * \param obj the Window java object
 * \param widget the window widget
 */
WindowDelegate::WindowDelegate(JNIEnv *env, jobject obj, QWidget *widget)
  : ContainerDelegate(env, obj, widget), m_acceptClose(true)
{
  qWidget()->setAttribute(Qt::WA_DeleteOnClose, false);
  qWidget()->setFocusPolicy(Qt::WheelFocus);
  sm_windows.append(this);
}

/*!
 * \brief Constructs new WindowDelegate object.
 *
 * \param env the JNIEnv pointer
 * \param obj the Window java object
 */
WindowDelegate::WindowDelegate(JNIEnv *env, jobject obj)
  : ContainerDelegate(env, obj, new QWidget(0, Qt::Window)), m_acceptClose(true)
{
  qWidget()->setAttribute(Qt::WA_DeleteOnClose, false);
  qWidget()->setFocusPolicy(Qt::WheelFocus);
  sm_windows.append(this);
}

// WindowDelegate :: Protected Destructor. -------------------------------------

/*!
 * \brief Destroys the WindowDelegate object.
 */
WindowDelegate::~WindowDelegate()
{
  if (notifyDelete() && eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
    windowClosed();
  }
  sm_windows.removeAt(sm_windows.lastIndexOf(this));
}

// WindowDelegate :: Native Calls. ---------------------------------------------

/*!
 * \brief Sets window type.
 *
 * \param env the JNIEnv pointer
 * \param windowType the window type
 */
void WindowDelegate::setWindowType(JNIEnv *env, jint windowType)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setWindowType, SWING_ENV_ARG, JINT_ARG(windowType))
  QIcon windowIcon = qWidget()->windowIcon();
  Qt::WindowFlags flags = qWidget()->windowFlags();
  flags = flags & (Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
  switch(windowType) { // avoids Qt::WindowFlags cast
    case com_cute4j_swing_Window_TYPE_WINDOW:
      flags = flags | Qt::Window;
      break;
    case com_cute4j_swing_Window_TYPE_DIALOG:
      flags = flags | Qt::Dialog;
      break;
    case com_cute4j_swing_Window_TYPE_POPUP:
      flags = flags | Qt::Popup;
      break;
    case com_cute4j_swing_Window_TYPE_TOOL:
      flags = flags | Qt::Tool;
      break;
    case com_cute4j_swing_Window_TYPE_SPLASH_SCREEN:
      flags = flags | Qt::SplashScreen;
      break;
  }
  qWidget()->setWindowFlags(flags);
  qWidget()->setWindowIcon(windowIcon);
}

/*!
 * \brief Sets window title.
 *
 * \param env the JNIEnv pointer
 * \param title the title
 */
void WindowDelegate::setTitle(JNIEnv *env, jstring title)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setTitle, SWING_ENV_ARG, JSTRING_ARG(title))
  if (title) {
    qWidget()->setWindowTitle(QStringAdapter(env, title));
  } else {
    qWidget()->setWindowTitle(" "); // avoids "javaw" title
  }
}

/*!
 * \brief Sets window icon.
 *
 * \param env the JNIEnv pointer
 * \param imagePointer the image pointer
 */
void WindowDelegate::setIconImage(JNIEnv *env, jlong imagePointer)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setIconImage, SWING_ENV_ARG, JLONG_ARG(imagePointer))
  if (imagePointer) {
    QPixmap *pixmap = SWING_IMAGE;
    if (pixmap->isNull()) {
      qWidget()->setWindowIcon(QApplication::windowIcon());
    } else {
      qWidget()->setWindowIcon(QIcon(*pixmap));
    }
  } else {
    qWidget()->setWindowIcon(QApplication::windowIcon());
  }
}

/*!
 * \brief Sets window icon.
 *
 * \param env the JNIEnv pointer
 * \param imageData the image data
 * \param length the image data length
 */
void WindowDelegate::setIconImage(JNIEnv *env, jbyteArray imageData,
                                  jint length)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setIconImage, SWING_ENV_ARG, JBYTEARRAY_ARG(imageData),
                  JINT_ARG(length))
  if (imageData) {
    QPixmap pixmap;
    if (SwingImageDelegate::loadImage(env, imageData, length, &pixmap)) {
      qWidget()->setWindowIcon(QIcon(pixmap));
      return;
    }
  }
  qWidget()->setWindowIcon(QApplication::windowIcon());
}

/*!
 * \brief Sets window opacity.
 *
 * \param env the JNIEnv pointer
 * \param opacity the opacity
 */
void WindowDelegate::setOpacity(JNIEnv *env, jdouble opacity)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setOpacity, SWING_ENV_ARG, JDOUBLE_ARG(opacity))
  qWidget()->setWindowOpacity(opacity);
}

/*!
 * \brief Sets whether or not the window can be closed.
 *
 * \param env the JNIEnv pointer
 * \param accept if \c true the window can be closed
 */
void WindowDelegate::setAcceptClose(JNIEnv *env, jboolean accept)
{
  Q_UNUSED(env);
  m_acceptClose = accept;
}

/*!
 * \brief Sets window modality.
 *
 * \param env the JNIEnv pointer
 * \param modality the modality
 */
void WindowDelegate::setModality(JNIEnv *env, jint modality)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setModality, SWING_ENV_ARG, JINT_ARG(modality))
  if ((modality >= Qt::NonModal) && (modality <= Qt::ApplicationModal)) {
    qWidget()->setWindowModality((Qt::WindowModality)modality);
  }
}

/*!
 * \brief Sets window state
 *
 * \param env the JNIEnv pointer
 * \param state the window state
 */
void WindowDelegate::setState(JNIEnv *env, jint state)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setState, SWING_ENV_ARG, JINT_ARG(state))
  switch (state) {
    case 0:
      qWidget()->setWindowState(Qt::WindowNoState);
      break;
    case 1:
      qWidget()->setWindowState(Qt::WindowMinimized);
      break;
    case 8:
      qWidget()->setWindowState(Qt::WindowMaximized);
      break;
    case 16:
      qWidget()->setWindowState(Qt::WindowFullScreen);
      break;
  }
}

/*!
 * \brief Sets whether or not the window is always on top.
 *
 * \param env the JNIEnv pointer
 * \param alwaysOnTop if \c true the window is always on top
 */
void WindowDelegate::setAlwaysOnTop(JNIEnv *env, jboolean alwaysOnTop)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setAlwaysOnTop, SWING_ENV_ARG, JBOOLEAN_ARG(alwaysOnTop))
  QWidget *widget = qWidget();
  bool visible = widget->isVisible();
  QIcon windowIcon = widget->windowIcon();
  Qt::WindowFlags flags = widget->windowType();
  if (alwaysOnTop) {
    flags |= Qt::WindowStaysOnTopHint;
    #ifdef CUTE4J_SWING_BYPASS_WINDOW_MANAGER_HINT
      flags |= Qt::X11BypassWindowManagerHint
    #endif // CUTE4J_SWING_BYPASS_WINDOW_MANAGER_HINT
  } else {
    flags &= ~Qt::WindowStaysOnTopHint;
    #ifdef CUTE4J_SWING_BYPASS_WINDOW_MANAGER_HINT
      flags &= Qt::X11BypassWindowManagerHint
    #endif // CUTE4J_SWING_BYPASS_WINDOW_MANAGER_HINT
  }
  widget->setWindowFlags(flags);
  widget->setWindowIcon(windowIcon);
  if (visible) {
    widget->setVisible(true);
  }
}

/*!
 * \brief Sets window location relative to the specified window.
 *
 * \param env the JNIEnv pointer
 * \param windowDelegate the referent window
 */
void WindowDelegate::setLocationRelativeTo(JNIEnv *env, jlong windowDelegate)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setLocationRelativeTo, SWING_ENV_ARG,
                  JLONG_ARG(windowDelegate))
  QWidget *widget = qWidget();
  QRect widgetGeometry = widget->frameGeometry();
  QRect centralGeometry;
  if (windowDelegate) {
    centralGeometry = WINDOW_CAST(windowDelegate)->qWidget()->frameGeometry();
  } else {
    int screenNumber = 0;
    QWidget *topWidget = widget->topLevelWidget();
    QDesktopWidget *desktopWidget = QApplication::desktop();
    if (topWidget) {
      screenNumber = desktopWidget->screenNumber(topWidget);
    } else if(desktopWidget->isVirtualDesktop()) {
      screenNumber = desktopWidget->screenNumber(QCursor::pos());
    } else {
      screenNumber = desktopWidget->screenNumber(widget);
    }
    centralGeometry = desktopWidget->availableGeometry(screenNumber);
  }
  widget->move(QPoint(centralGeometry.x() +
                      (centralGeometry.width() - widgetGeometry.width()) / 2,
                      centralGeometry.y() +
                      (centralGeometry.height() -
                                                widgetGeometry.height()) / 2));
}

/*!
 * \brief Brings the window to front.
 *
 * \param env the JNIEnv pointer
 */
void WindowDelegate::toFront(JNIEnv *env)
{
  Q_ASSERT(env);
  SWING_REENTRANT(toFront, SWING_ENV_ARG)
  qWidget()->raise();
  qWidget()->activateWindow();
}

/*!
 * \brief Moves the window to back.
 *
 * \param env the JNIEnv pointer
 */
void WindowDelegate::toBack(JNIEnv *env)
{
  Q_ASSERT(env);
  SWING_REENTRANT(toBack, SWING_ENV_ARG)
  qWidget()->clearFocus();
  qWidget()->lower();
}

/*!
 * \brief Sets border decoration.
 *
 * \param env the JNIEnv pointer
 * \param borderDecoration the border decoration
 */
void WindowDelegate::setBorderDecoration(JNIEnv *env, jint borderDecoration)
{
  Q_ASSERT(env);
  SWING_REENTRANT(setBorderDecoration, SWING_ENV_ARG,
                  JINT_ARG(borderDecoration))
  QIcon windowIcon = qWidget()->windowIcon();
  Qt::WindowFlags flags = qWidget()->windowFlags();
  flags = flags & (Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint |  // TODO: check bypass hint
                   Qt::WindowType_Mask);
  qWidget()->setWindowFlags(flags | Qt::WindowFlags(borderDecoration));
  qWidget()->setWindowIcon(windowIcon);
}

// WindowDelegate :: Static Native Calls. --------------------------------------

/*!
 * \brief Initializes WindowDelegate class.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 *
 * \exception FatalError if failed
 */
void WindowDelegate::initialize(JNIEnv *env, jclass cls)
{
  Q_ASSERT(env);
  Q_ASSERT(cls);
  Q_ASSERT(!sm_windowClass);
  if (!INIT_CLASS(window, cls) ||
      !INIT_UMID(windowOpened, cls, "()V") ||
      !INIT_UMID(windowClosing, cls, "()V") ||
      !INIT_UMID(windowClosed, cls, "()V") ||
      !INIT_UMID(windowActivated, cls, "()V") ||
      !INIT_UMID(windowDeactivated, cls, "()V") ||
      !INIT_UMID(windowGainedFocus, cls, "()V") ||
      !INIT_UMID(windowLostFocus, cls, "()V") ||
      !INIT_UMID(windowStateChanged, cls, "(II)V")) {
    FATAL_LOGGED_ERROR("WindowDelegate initialization failed");
  }
}

/*!
 * \brief Returns \c true if the system supports window opacity.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * 
 * \return \c true if the sitem supports window opacity
 */
jboolean WindowDelegate::opacitySupported(JNIEnv *env, jclass cls)
{
  Q_UNUSED(env);
  Q_UNUSED(cls);
  #ifdef Q_OS_WIN32
    return (QSysInfo::WindowsVersion >= QSysInfo::WV_2000);
  #else
    return true;
  #endif // Q_OS_WIN32
}

/*!
 * \brief Returns \c true if the system supports always on top windows.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * 
 * \return \c true if the system supports always on top windows
 */
jboolean WindowDelegate::alwaysOnTopSupported(JNIEnv *env, jclass cls)
{
  Q_UNUSED(env);
  Q_UNUSED(cls);
  return true;
}

/*!
 * \brief Returns \c true if the system supports full screen windows.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * 
 * \return \c true if the system supports full screen windows
 */
jboolean WindowDelegate::fullScreenSupported(JNIEnv *env, jclass cls)
{
  Q_UNUSED(env);
  Q_UNUSED(cls);
  return true;
}

/*!
 * \brief \c true if the system supports non-rectangular windows.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * 
 * \return \c true if the system supports non-rectangular windows
 */
jboolean WindowDelegate::windowShapeSupported(JNIEnv *env, jclass cls)
{
  Q_UNUSED(env);
  Q_UNUSED(cls);
  return true;
}

/*!
 * \brief Returns all non deleted windows.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * 
 * \return all non deleted windows
 */
jobjectArray WindowDelegate::getWindows(JNIEnv *env, jclass cls)
{
  Q_ASSERT(env);
  SWING_STATIC_REENTRANT_RETURN(WindowDelegate, getWindows, jobjectArray,
                                SWING_ENV_ARG, JCLASS_ARG(cls))
  int windowCount = sm_windows.size();
  jobjectArray jwindows = JNIWrapper::newObjectArray(env, sm_windowClass,
                                                     windowCount);
  if (jwindows) {
    for (int i = 0; i < windowCount; i++) {
      env->SetObjectArrayElement(jwindows, i,
                                 sm_windows.at(i)->jniObject()->ref());
    }
  }
  return jwindows;
}

/*!
 * \brief Returns active window.
 *
 * \return active window
 */
jobject WindowDelegate::getActiveWindow(JNIEnv *env, jclass cls)
{
  Q_ASSERT(env);
  SWING_STATIC_REENTRANT_RETURN(WindowDelegate, getActiveWindow, jobject,
                                SWING_ENV_ARG, JCLASS_ARG(cls))
  QWidget *activeWindow = QApplication::activeWindow();
  if (activeWindow) {
    WindowDelegate *windowDelegate =
                        qObjectDelegatePointer<WindowDelegate *>(activeWindow);
    if (windowDelegate) {
      return windowDelegate->jniObject()->ref();
    }
  }
  return 0;
}

/*!
 * \brief Sets the owner of the specified window.
 *
 * \param env the JNIEnv pointer
 * \param cls the Window java class
 * \param windowDelegate the window delegate pointer
 * \param ownerDelegate the owner delegate pointer
 */
void WindowDelegate::setWindowOwner(JNIEnv *env, jclass cls, jlong windowDelegate,
                                    jlong ownerDelegate)
{
  Q_ASSERT(env);
  SWING_STATIC_REENTRANT(WindowDelegate, setWindowOwner, SWING_ENV_ARG,
                         JCLASS_ARG(cls), JLONG_ARG(windowDelegate),
                         JLONG_ARG(ownerDelegate))
  WindowDelegate *windowDelegatePointer = WINDOW_CAST(windowDelegate);
  QWidget *window = windowDelegatePointer->qWidget();
  QIcon windowIcon = window->windowIcon();
  bool visible = window->isVisible();
  if (ownerDelegate) {
    WindowDelegate *ownerDelegatePointer = WINDOW_CAST(ownerDelegate);
    window->setParent(ownerDelegatePointer->qWidget(), window->windowFlags());
    windowDelegatePointer->setParentDelegate(ownerDelegatePointer);
  } else {
    window->setParent(0, window->windowFlags());
    windowDelegatePointer->setParentDelegate(0);
  }
  window->setWindowIcon(windowIcon);
  if (visible) {
    window->setVisible(true);
  }
}

/*!
 * \brief Returns new instance of the delegate object
 *
 * \param env the JNIEnv pointer
 * \param obj the Swing object
 * 
 * \return new instance of the delegate object
 */
jlong WindowDelegate::newInstance(JNIEnv *env, jobject obj)
{
  if (JNISwingWrapper::isSwingThread(env)) {
    return reinterpret_cast<jlong>(new WindowDelegate(env, obj));
  }
  return SwingUtilitiesDelegate::invokeLongAndWait(env, obj, createMID());
}

// WindowDelegate :: Native Callbacks. -----------------------------------------

/*!
 * \brief Calls windowOpened method of the Component java object.
 */
void WindowDelegate::windowOpened()
{
  jniObject()->callVoidMethod(sm_windowOpenedMID);
}

/*!
 * \brief Calls windowClosing method of the Component java object.
 */
void WindowDelegate::windowClosing()
{
  jniObject()->callVoidMethod(sm_windowClosingMID);
}

/*!
 * \brief Calls windowClosed method of the Component java object.
 */
void WindowDelegate::windowClosed()
{
  jniObject()->callVoidMethod(sm_windowClosedMID);
}

/*!
 * \brief Calls windowActivated method of the Component java object.
 */
void WindowDelegate::windowActivated()
{
  jniObject()->callVoidMethod(sm_windowActivatedMID);
}

/*!
 * \brief Calls windowDeactivated method of the Component java object.
 */
void WindowDelegate::windowDeactivated()
{
  jniObject()->callVoidMethod(sm_windowDeactivatedMID);
}

/*!
 * \brief Calls windowGainedFocus method of the Component java object.
 */
void WindowDelegate::windowGainedFocus()
{
  jniObject()->callVoidMethod(sm_windowGainedFocusMID);
}

/*!
 * \brief Calls windowLostFocus method of the Component java object.
 */
void WindowDelegate::windowLostFocus()
{
  jniObject()->callVoidMethod(sm_windowLostFocusMID);
}

/*!
 * \brief Calls windowStateChanged method of the Component java object.
 *
 * \param oldState the old window state
 * \param newState the new window state
 */
void WindowDelegate::windowStateChanged(jint oldState, jint newState)
{
  jniObject()->callVoidMethod(sm_windowStateChangedMID, oldState, newState);
}

// WindowDelegate :: Overridden Member Functions. ------------------------------

/*!
 * \inheritDoc
 */
bool WindowDelegate::filterEvent(QObject *obj, QEvent *e)
{
  bool filtered = true;
  switch (e->type()) {
    case QEvent::Close:
      windowClosing();
      filtered = obj->event(e);
      if (m_acceptClose) {
        e->accept();
      } else {
        e->ignore();
      }
      break;
    case QEvent::WindowActivate:
      filtered = obj->event(e);
      if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
        windowActivated();
      }
      break;
    case QEvent::WindowDeactivate:
      filtered = obj->event(e);
      if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
        windowDeactivated();
      }
      break;
    case QEvent::FocusIn:
      filtered = obj->event(e);
      if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
        windowGainedFocus();
      }
      focusGained(); // little opt.
      break;
    case QEvent::FocusOut:
      filtered = obj->event(e);
      if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
        windowLostFocus();
      }
      focusLost(); // little opt.
      break;
    case QEvent::WindowStateChange:
      filtered = obj->event(e);
      if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
        QWindowStateChangeEvent *wsce =
                                     static_cast<QWindowStateChangeEvent *>(e);
        Qt::WindowStates oldState = wsce->oldState();
        Qt::WindowStates newState = qWidget()->windowState();
        Qt::WindowStates oldActiveState = (oldState & Qt::WindowActive);
        Qt::WindowStates newActiveState = (newState & Qt::WindowActive);
        oldState = (oldState & ~Qt::WindowActive);
        newState = (newState & ~Qt::WindowActive);
        if (oldState != newState) {
          windowStateChanged(converWindowState(oldState),
                             converWindowState(newState));
        }
        if (oldActiveState != newActiveState) {
          if (newActiveState) {
            windowActivated();
          } else {
            windowDeactivated();
          }
        }
      }
      break;
    default:
      filtered = ContainerDelegate::filterEvent(obj, e);
  }
  return filtered;
}

/*!
 * \inheritDoc
 */
void WindowDelegate::showWindow(JNIEnv *env)
{
  Q_ASSERT(env);
  SWING_REENTRANT(showWindow, SWING_ENV_ARG)
  if (!qWidget()->testAttribute(Qt::WA_Resized)) {
    qWidget()->adjustSize();
  }
  qWidget()->setVisible(true);
  if (eventEnabled(SWING_EVENT(WINDOW_EVENT_MASK))) {
    windowOpened();
  }
  qWidget()->setFocus(Qt::ActiveWindowFocusReason);
}

// WindowDelegate :: Private Member Function. ----------------------------------

jint WindowDelegate::converWindowState(Qt::WindowStates states)
{
  switch(states) {
    case Qt::WindowMinimized:
      return 1;
    case Qt::WindowMaximized:
      return 8;
    case Qt::WindowFullScreen:
      return 16;
    default:
      return 0;
  }
}

// WindowDelegate :: Private Static Data Members. ------------------------------

QList<WindowDelegate *> WindowDelegate::sm_windows;
jclass WindowDelegate::sm_windowClass = 0;
jmethodID WindowDelegate::sm_windowOpenedMID = 0;
jmethodID WindowDelegate::sm_windowClosingMID = 0;
jmethodID WindowDelegate::sm_windowClosedMID = 0;
jmethodID WindowDelegate::sm_windowActivatedMID = 0;
jmethodID WindowDelegate::sm_windowDeactivatedMID = 0;
jmethodID WindowDelegate::sm_windowGainedFocusMID = 0;
jmethodID WindowDelegate::sm_windowLostFocusMID = 0;
jmethodID WindowDelegate::sm_windowStateChangedMID = 0;
